#pragma once

#include <cstddef>
#include <array>
#include <tuple>

#include "../utils/misc.h"

namespace mdtx::online::impl
{
    template <typename T>
    class m4
    {
    public:
        ptrdiff_t n;
        std::array<T, 4> raw;
        kahan<T> acc_u1, acc_u2, acc_u3, acc_u4;

        auto center_impl() const
        {
            T u_2 = raw[0] * raw[0];
            T u_3 = raw[0] * u_2;
            T u_4 = u_2 * u_2;

            return std::array<T, 4>{
                raw[0],
                raw[1] - u_2,
                raw[2] - 3 * raw[0] * raw[1] + 2 * u_3,
                raw[3] - 4 * raw[0] * raw[2] + 6 * u_2 * raw[1] - 3 * u_4};
        }

        void insert_impl(T x, T x2, T x3, T x4)
        {
            T d;
            n += 1;

            d = x - raw[0];
            acc_u1(raw[0], d / n);

            d = x2 - raw[1];
            acc_u2(raw[1], d / n);

            d = x3 - raw[2];
            acc_u3(raw[2], d / n);

            d = x4 - raw[3];
            acc_u4(raw[3], d / n);
        }

        void remove_impl(T x, T x2, T x3, T x4)
        {
            T d;
            n -= 1;

            d = raw[0] - x;
            acc_u1(raw[0], d / n);

            d = raw[1] - x2;
            acc_u2(raw[1], d / n);

            d = raw[2] - x3;
            acc_u3(raw[2], d / n);

            d = raw[3] - x4;
            acc_u4(raw[3], d / n);
        }

        void roll_impl(T x, T x2, T x3, T x4,
                       T y, T y2, T y3, T y4)
        {
            T d;

            d = x - y;
            acc_u1(raw[0], d / n);

            d = x2 - y2;
            acc_u2(raw[1], d / n);

            d = x3 - y3;
            acc_u3(raw[2], d / n);

            d = x4 - y4;
            acc_u4(raw[3], d / n);
        }

        void reset_impl()
        {
            raw.fill(0);
            acc_u1.reset();
            acc_u1.reset();
            acc_u3.reset();
            acc_u4.reset();
        }
    };
}
